﻿//PlazaTable.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Plaza.Metadata
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Reflection;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Serialization;
	using Ecng.Reflection;

	using StockSharp.BusinessEntities;
	using StockSharp.Plaza.Wrappers;

	/// <summary>
	/// Таблица, описывающая данные с сервера РТС.
	/// </summary>
	[EntityFactory(typeof(UnitializedEntityFactory<PlazaTable>))]
	[TypeSchemaFactory(SearchBy.Properties, VisibleScopes.Both)]
	public class PlazaTable
	{
		internal PlazaTable(string name, string description, PlazaStream stream, PlazaColumns metadata, params SecurityTypes[] securityTypes)
		{
			if (name.IsEmpty())
				throw new ArgumentNullException("name");

			if (description.IsEmpty())
				throw new ArgumentException("description");

			if (stream == null)
				throw new ArgumentNullException("stream");

			if (metadata == null)
				throw new ArgumentNullException("metadata");

			if (securityTypes == null)
				throw new ArgumentNullException("securityTypes");

			Name = name;
			Description = description;
			Stream = stream;
			Metadata = metadata;
			SecurityTypes = securityTypes;
			Id = Stream.Name + "--" + Name;

			Columns = new PlazaColumnList { Table = this };

			Stream.TableList.Add(this);

			Columns.AddRange(metadata
					.GetType()
					.GetMembers<FieldInfo>(BindingFlags.Public | BindingFlags.Instance)
					.OrderBy(f => GetParentLevel(f.DeclaringType)) // колонки replID, replRev, replAct должны идти первыми
					.Select(f => (PlazaColumn)f.GetValue(metadata))
					.Where(c => c.IsMandatory));

			IsFuture = securityTypes.Count() == 1 && SecurityTypes.Single() == BusinessEntities.SecurityTypes.Future;
		}

		private static int GetParentLevel(Type t)
		{
			if (t == typeof(object))
				return 0;
			else
				return GetParentLevel(t.BaseType) + 1;
		}

		internal PlazaStream Stream { get; private set; }
		internal PlazaColumns Metadata { get; private set; }

		/// <summary>
		/// Уникальный идентификатор таблицы.
		/// </summary>
		public string Id { get; private set; }

		/// <summary>
		/// Имя таблицы.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		/// Имя потока данных.
		/// </summary>
		public string StreamName { get { return Stream.Name; } }

		/// <summary>
		/// Текстовое название таблицы.
		/// </summary>
		public string Description { get; private set; }

		/// <summary>
		/// Типы инструментов, для которых предназначены данные в таблице.
		/// Если таблица не передается связанные с инструментами данные, то будет возвращено пустое множество.
		/// </summary>
		public IEnumerable<SecurityTypes> SecurityTypes { get; private set; }

		// для ускорения
		internal readonly bool IsFuture;

		/// <summary>
		/// Список колонок, которые необходимо передать с сервера Plaza.
		/// </summary>
		public PlazaColumnList Columns { get; private set; }

		private long _revision;

		/// <summary>
		/// Идентификатор последнего изменения таблицы.
		/// </summary>
		public long Revision
		{
			get { return _revision; }
			internal set
			{
				if (_revision != value)
				{
					_revision = value;
					RevisionChanged.SafeInvoke();
				}
			}
		}

		/// <summary>
		/// Событие начала обработки данных.
		/// </summary>
		public event Action Begin;

		/// <summary>
		/// Событие окончания обработки данных.
		/// </summary>
		public event Action<PlazaTable> End;

		/// <summary>
		/// Событие удаления данных.
		/// </summary>
		public event Action<PlazaTable, long> Cleared;

		/// <summary>
		/// Событие вставки новых данных.
		/// </summary>
		public event Action<PlazaRecord> Inserted;

		/// <summary>
		/// Событие удаления данных.
		/// </summary>
		public event Action<PlazaRecord> Deleted;

		/// <summary>
		/// Событие изменения <see cref="Revision"/>.
		/// </summary>
		public event Action RevisionChanged;

		/// <summary>
		/// Событие сброса состояния всего потока.
		/// </summary>
		public event Action<PlazaTable> Flushed;

		private void StreamBegin()
		{
			Begin.SafeInvoke();
		}

		private void StreamEnd()
		{
			End.SafeInvoke(this);
		}

		/// <summary>
		/// Писать RAW-логфайл с данными таблицы. Имя файла совпадает с названием таблицы. По умолчанию false.
		/// </summary>
		public bool LogTableData { get; set; }

		private void LogData(string file, PlazaRecord record)
		{
			bool isNew = !File.Exists(file);

			using(var sw = new StreamWriter(file, true))
			{

				if (isNew)
				{
					foreach (var col in Columns)
						sw.Write(col.Name+";");
					sw.WriteLine();
				}

				foreach (var col in Columns)
				{
					sw.Write(record.Get<object>(col)+";");
				}
				sw.WriteLine();
				sw.Close();
			}
		}

		private void StreamInserted(string tableName, IRecordWrapper record)
		{
			if (Name == tableName)
			{
				var evt = Inserted;

				if (evt != null)
				{
					var plazaRecord = CreateRecord(record);
					var replRev = plazaRecord.ReplRevision;

					if (LogTableData)
						LogData(tableName, plazaRecord);

					if (Revision < replRev)
					{
						Revision = replRev;
						evt(plazaRecord);
					}
					else
						throw new ArgumentOutOfRangeException("record", replRev, "Новая запись для таблицы {0} имеет неправильную ревизию {1}.".Put(this, Revision));
				}
			}
		}

		private void StreamDeleted(string tableName, IRecordWrapper record)
		{
			if (Name == tableName)
			{
				Deleted.SafeInvoke(CreateRecord(record));
			}
		}

		private void StreamFlushed()
		{
			// P2ClientGate.doc стр. 18:
			// «Безбазовый» клиент должен изменить номер жизни с помощью вызова P2TableSet-> SetLifeNumToIni,
			// удалить все данные из своего хранилища и обнулить начальные ревизии
			Revision = 0;

			Flushed.SafeInvoke(this);
		}

		private void StreamCleared(string tableName, long revision)
		{
			if (Name == tableName)
			{
				Revision = revision - 1;
				Cleared.SafeInvoke(this, revision);
			}
		}

		private PlazaRecord CreateRecord(IRecordWrapper record)
		{
			return new PlazaRecord(this, record);
		}

		/// <summary>
		/// Получить строковое представление.
		/// </summary>
		/// <returns>Строковое представление.</returns>
		public override string ToString()
		{
			return Id;
		}

		internal void Subscribe(PlazaStream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			stream.Begin += StreamBegin;
			stream.End += StreamEnd;
			stream.Flushed += StreamFlushed;
			stream.Inserted += StreamInserted;
			stream.Deleted += StreamDeleted;
			stream.Cleared += StreamCleared;
		}

		internal void UnSubscribe(PlazaStream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			stream.Begin -= StreamBegin;
			stream.End -= StreamEnd;
			stream.Flushed -= StreamFlushed;
			stream.Inserted -= StreamInserted;
			stream.Deleted -= StreamDeleted;
			stream.Cleared -= StreamCleared;
		}
	}
}